package com.yoyo.itravel.web.controller;

import com.yoyo.itravel.utils.StringUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @Description 文件上传服务端--文件上传只能使用POST方式
 *
 *    1.如何保证服务器文件安全
 *          解决办法：将保存上传文件的目录放到用户直接访问不到的地方
 *    2.如何避免上传文件同名覆盖问题
 *          解决办法：让文件名唯一
 *    3.避免同一个目录下文件过多
 *          解决办法1：按照日期进行打散存储
 *          解决办法2：用文件名的hashcode进行创建打散的存储目录，再放入文件
 *
 * @author HZX
 * @date 2020/5/10 15:29
 */
@Controller
@RequestMapping("/upload")
public class UpLoadController {

    private static final long serialVersionUID = 3128095486356952835L;

    // 文件上传的最大限制
    final long MAX_SIZE = 10 * 1024 * 1024 * 1024; // 设置上传文件最大为 10G
    // 将上传的文件保存到WEB-INF目录下的upload文件夹【/WEB-INF目录无法直接通过浏览器进行访问，可以保证文件的安全】
    private static final String FILE_UPLOAD_PATH = "/upload";

    @RequestMapping("/uploadFile")
    protected void upload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 判断是否是支持上传的表单，要有：enctype="multipart/form-data"
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);

        if (!isMultipartContent) {
            // 不支持文件上传，则抛出异常
            throw new RuntimeException("服务器升级，无法上传资源...");
        } else { // 支持文件上传
            /** 1.创建一个DiskFileItemFactory **/
            DiskFileItemFactory factory = new DiskFileItemFactory();
            /** 2.通过factory对象创建一个解析器对象 **/
            ServletFileUpload fileUpload = new ServletFileUpload(factory);

            // 解决乱码
            fileUpload.setHeaderEncoding("UTF-8");
            fileUpload.setSizeMax(MAX_SIZE);

            try {
                /** 3.使用解析器解析对象 **/
                List<FileItem> fileItems = fileUpload.parseRequest(request);
                // 遍历所有的表单项
                for (FileItem fileItem : fileItems) {
                    if (fileItem.isFormField()) { // 普通文本
                        // 处理普通文本表单项
                        handlePlainItem(fileItem);
                    } else {
                        // 处理文件上传表单项
                        handleUploadItem(fileItem, request);
                    }
                }
            } catch (FileUploadException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @Description 处理文件上传表单项
     *
     * @param fileItem
     * @return void
     */
    private void handleUploadItem(FileItem fileItem, HttpServletRequest request) {
        // 声明字节输入输出流对象
        InputStream is = null;
        OutputStream os = null;

        try {
            // 实例化输入流对象
            is = fileItem.getInputStream();
            // 获得文件上传的绝对路径
            String upload_folder_path = request.getServletContext().getRealPath(FILE_UPLOAD_PATH);

            // 构建File对象
            File uploadFile = new File(upload_folder_path);
            // 判断uploadFile文件夹是否存在
            if (!uploadFile.exists()) {
                // 不存在，则直接创建文件夹
                uploadFile.mkdir();
            }

            // 获得上传文件的文件名
            String fileName = fileItem.getName();
//            System.out.println(fileName);
//            // 判断fileName是否为null
//            if (fileName != null) {
//                // 获得文件
//            }
            // 为避免文件同名，在文件名的左边添加一个UUID，使文件名唯一
            fileName = StringUtils.getRandomUUID() + "_" + fileName;

            // 【方法一】使用日期划分文件夹，防止一个文件夹下面文件过多
            upload_folder_path = formatFolderByDate(upload_folder_path);

            // 构建一个服务器上文件的全路径
            String filePathOnServer = upload_folder_path + File.separator + fileName;

            // 实例化输出流对象
            os = new FileOutputStream(filePathOnServer);

            // 边读边写
            int len = -1;
            byte[] buf = new byte[1024 * 10];

            while ((len = is.read(buf)) != -1) {
                os.write(buf, 0, len);
            }
            os.flush();
            os.close();
            is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description 使用当前日期作为上传文件的二级目录
     *
     * @param upload_folder_path
     * @return java.lang.String
     */
    private String formatFolderByDate(String upload_folder_path) {
        // 得到当前日期的年月日
        String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        // 构建一个File对象--在upload_folder_path下建立一个名称为date的文件夹
        File file = new File(upload_folder_path, date);
        // 判断是否存在
        if (!file.exists()) {
            file.mkdir();
        }
        // 返回该文件所指向的路径
        return file.toString();
    }

    /**
     * 按照文件名的hashcode值作为folderName的二级目录
     *
     * @param upload_folder_path
     * @param fileName
     * @return
     */
    private String formatFolderByHashCode(String upload_folder_path, String fileName) {
        // 得到32位的hashcode
        int hashCode = fileName.hashCode();
        // 转成十六进制，并且全部变成大写
        String hexString = Integer.toHexString(hashCode).toUpperCase();
        // 取出前面2位，作为二级和三级目录
        String path = hexString.charAt(0) + File.separator + hexString.charAt(1);
        // 构建新的文件路径
        File file = new File(upload_folder_path, path);
        // 判断是否存在
        if (!file.exists()) {
            file.mkdirs();// 创建多级目录
        }
        return file.toString();
    }

    /**
     * @Description 处理普通文本表单项
     *
     * @param fileItem
     * @return void
     */
    private void handlePlainItem(FileItem fileItem) {
        // 得到表单项名称
        String fiedlName = fileItem.getFieldName();
        try {
            // 得到属性值
            String fieldValue = fileItem.getString("UTF-8");
            System.out.println(fiedlName + "=" + fieldValue);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

}
